package com.googlecode.tapestry5validator;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Locale;

import org.apache.tapestry5.Asset;
import org.apache.tapestry5.ComponentResources;
import org.apache.tapestry5.Field;
import org.apache.tapestry5.FieldValidator;
import org.apache.tapestry5.internal.services.CompositeFieldValidator;
import org.apache.tapestry5.ioc.AnnotationProvider;
import org.apache.tapestry5.ioc.Invocation;
import org.apache.tapestry5.ioc.Messages;
import org.apache.tapestry5.ioc.MethodAdvice;
import org.apache.tapestry5.ioc.MethodAdviceReceiver;
import org.apache.tapestry5.ioc.annotations.Order;
import org.apache.tapestry5.ioc.services.ThreadLocale;
import org.apache.tapestry5.services.AssetSource;
import org.apache.tapestry5.services.FormSupport;
import org.slf4j.Logger;


public class TapestryValidatorModule {

	
	public static ValidatorSearchersSource buildSupportedHibernateValidatorsProvider(
	        List<ValidatorSearcher> searchers) {
		return new ValidatorSearchersSource(searchers);
	}
	
	/**
	 * Add JS for extending tapestry client validation. 
	 */
	@SuppressWarnings("unchecked")
	@Order("after:*")
	public static void adviseClientInfrastructure(MethodAdviceReceiver receiver,
	                                              final AssetSource assetSource, 
	                                              final ThreadLocale threadLocale) {

		MethodAdvice advice =
			new MethodAdvice() {

				@Override
				public void advise(Invocation invocation) {
					invocation.proceed();
					List<Asset> assets = (List<Asset>) invocation.getResult();
					if (assets != null && !assets.isEmpty()) {
						
					Asset jsValidator = assetSource
						.getAsset(	null,
									"com/googlecode/tapestry5validator/tapestry-validator.js",
									threadLocale.getLocale());
					
						assets.add(jsValidator);
					}
					
					invocation.overrideResult(assets);
				}
			
			};
			
			Method method;
			
			try {
				method = receiver.getInterface().getMethod("getJavascriptStack");
			} catch (SecurityException e) {
				throw new RuntimeException(e);	
			} catch (NoSuchMethodException e) {
				throw new RuntimeException(String.format("Can't find method FieldValidatorDefaultSource" +
				                     					".createDefaultValidator(Field, String, Messages, Locale, " +
				                    					"Class, AnnotationProvider). Changed API?", ""), e);
			}
		
			receiver.adviseMethod(method, advice);
	}
	
	/**
	 * Add advice for including hibernate validators to list field validators. 
	 */
	@SuppressWarnings("unchecked")
	@Order("after:*")
	public static void adviseFieldValidatorDefaultSource(MethodAdviceReceiver receiver,
	                                                     final FormSupport formSupport,
	                                                     final ValidatorSearchersSource searchersSource,
	                                                     final Logger log) {
		MethodAdvice advice =
			new MethodAdvice() {
	
				@Override
				@SuppressWarnings("unused")
				public void advise(Invocation invocation) {
					
					invocation.proceed();
					
					//PARAMETRS
					Field field = (Field) invocation.getParameter(0);
					String overrideId = (String) invocation.getParameter(1);
			        Messages overrideMessages = (Messages) invocation.getParameter(2);
			        Locale locale = (Locale) invocation.getParameter(3);
			        Class propertyType = (Class) invocation.getParameter(4);
			        AnnotationProvider propertyAnnotations = (AnnotationProvider) invocation.getParameter(5);

			        for (ValidatorSearcher vs : searchersSource.getSearchers()) {
			        	
						Collection<FieldValidator<?>> validators = vs.find(field,
								overrideId, formSupport, overrideMessages, locale,
								propertyAnnotations);
		
						applyValidators(invocation, validators);
						
			        }
			        
					
					
					
				}
	
			};
			
		Method method = findMethod(	receiver.getInterface(),
									"createDefaultValidator",
									Field.class,
									String.class,
									Messages.class,
									Locale.class,
									Class.class,
									AnnotationProvider.class);			
		
			
		receiver.adviseMethod(method, advice);
		
		advice = new MethodAdvice() {

			@Override
			public void advise(Invocation invocation) {
				invocation.proceed();
				
				//PARAMETRS
				ComponentResources resources = (ComponentResources) invocation.getParameter(0); 
				String parameterName = (String) invocation.getParameter(1);

		        Class propertyType = resources.getBoundType(parameterName);

		        if (propertyType == null) return;

		        Field field = (Field) resources.getComponent();
		        
		        for (ValidatorSearcher vs : searchersSource.getSearchers()) {
					
					Collection<FieldValidator<?>> validators = vs.find(
							field, 
							resources.getId(), formSupport, 
							resources.getContainerMessages(), 
							resources.getLocale(), 
							resources.getAnnotationProvider(parameterName));
				
					applyValidators(invocation, validators);
		        }
			}
			
		};
		
		method = findMethod(receiver.getInterface(),
							"createDefaultValidator",
							ComponentResources.class,
							String.class);

		receiver.adviseMethod(method, advice);
		
	}
	
	private static Method findMethod(Class<?> clazz, String methodName, Class<?>...params) {
		try {
			return clazz.getMethod(methodName, params);
		} catch (SecurityException e) {
			throw new RuntimeException(e);	
		} catch (NoSuchMethodException e) {
			throw new RuntimeException(String.format("Can't find method %s"+ 
			                     					".%s(%s). Changed API?", 
			                     					clazz.getSimpleName(),
			                     					methodName,
			                     					params), e);
		}
	}

	
	@SuppressWarnings("unchecked")
	private static void applyValidators(Invocation invocation,
	                                             Collection<FieldValidator<?>> hibernateValidators) {
		if (!hibernateValidators.isEmpty()) {

			FieldValidator result = (FieldValidator) invocation.getResult();

			List<FieldValidator> validators = new ArrayList<FieldValidator>();

			validators.add(result);
			validators.addAll(hibernateValidators);

			CompositeFieldValidator newResult = new CompositeFieldValidator(validators);

			invocation.overrideResult(newResult);

		}
	}	
	
}
